﻿// -----------------------------------------------------------------------
// <copyright file="Visitor.Statements.cs" company="none">
// Copyright (c) 2012 M. Alberti, xanatos(at)live.it
// Based on the articles found at 
// http://blogs.msdn.com/b/mitsu/archive/2010/03/02/c-4-expressions-blocks-part-i.aspx, 
// http://blogs.msdn.com/b/mitsu/archive/2010/03/02/c-4-expressions-variables-part-ii.aspx, and
// http://blogs.msdn.com/b/mitsu/archive/2010/03/02/c-4-expressions-loops-goto-label-if-and-even-for-part-iii.aspx.
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
// </copyright>
// -----------------------------------------------------------------------

namespace FluentStatement
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Diagnostics.CodeAnalysis;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;

    /// <summary>
    /// 
    /// </summary>
    [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1118:ParameterMustNotSpanMultipleLines", Justification = "More readable this way")]
    public partial class Visitor : ExpressionVisitor
    {
        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo IDisposableDispose = typeof(IDisposable).GetMethod("Dispose");

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression StartStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            Expression expression = this.VisitBodyLambda(node.Arguments[0]);
            return expression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression EmptyStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            return Expression.Empty();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression DoStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            Expression expression = this.VisitBodyLambda(node.Arguments[0]);
            return expression ?? Expression.Empty();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression ReturnStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            this.ReturnLabelUsed = true;

            Expression expression = Expression.Return(this.ReturnLabel);
            return expression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression ReturnTStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            this.ReturnLabelUsed = true;

            Expression ret = this.Visit(node.Arguments[0]);
            Expression expression = Expression.Return(this.ReturnLabel, ret, ret != null ? ret.Type : typeof(void));
            return expression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression InvokeStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            ////    // This variant is a "real" Invoke.
            ////    Expression invoked = (Expression)Expression.Lambda(node.Arguments[0]).Compile().DynamicInvoke();
            ////    NewArrayExpression arguments = this.ExpressionOfType<NewArrayExpression>(this.Visit(node.Arguments[1]));

            ////    LambdaExpression invoked2 = this.ExpressionOfType<LambdaExpression>(invoked);

            ////    if (arguments.Expressions.Count != invoked2.Parameters.Count)
            ////    {
            ////        throw new ArgumentException();
            ////    }

            ////    var arguments2 = new Expression[arguments.Expressions.Count];

            ////    for (int i = 0; i < arguments.Expressions.Count; i++)
            ////    {
            ////        Expression argument = this.Visit(arguments.Expressions[i]);

            ////        if (argument.Type != invoked2.Parameters[i].Type && argument.NodeType == ExpressionType.Convert && argument.Type == typeof(object) && argument is UnaryExpression)
            ////        {
            ////            argument = ((UnaryExpression)argument).Operand;
            ////        }

            ////        arguments2[i] = argument;
            ////    }

            ////    Expression expression = Expression.Invoke(invoked2, arguments2);
            ////    return expression;

            // This variant is an Invoke through Inclusion
            Expression invoked = (Expression)Expression.Lambda(node.Arguments[0]).Compile().DynamicInvoke();
            NewArrayExpression arguments = this.ExpressionOfType<NewArrayExpression>(this.Visit(node.Arguments[1]));

            LambdaExpression invoked2 = this.ExpressionOfType<LambdaExpression>(invoked);

            if (arguments.Expressions.Count != invoked2.Parameters.Count)
            {
                throw new ArgumentException();
            }

            Expression expression;

            if (invoked2.Parameters.Count == 0)
            {
                expression = this.VisitBodyLambda(invoked2);
            }
            else
            {
                var block = new List<Expression>();

                for (int i = 0; i < arguments.Expressions.Count; i++)
                {
                    Expression argument = this.Visit(arguments.Expressions[i]);

                    if (argument.Type != invoked2.Parameters[i].Type && argument.NodeType == ExpressionType.Convert && argument.Type == typeof(object) && argument is UnaryExpression)
                    {
                        argument = ((UnaryExpression)argument).Operand;
                    }

                    block.Add(Expression.Assign(invoked2.Parameters[i], argument));
                }

                block.Add(this.VisitBodyLambda(invoked2));

                expression = Expression.Block(invoked2.Parameters, block);
            }

            return expression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression BlockStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            Expression expression = this.Visit(node.Arguments[0]) ?? Expression.Empty();
            return expression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression BlockTStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            Expression locals = node.Arguments[0];
            LambdaExpression body = this.ExpressionOfType<LambdaExpression>(node.Arguments[1]);

            if (locals == null && body == null)
            {
                return Expression.Empty();
            }

            ParameterExpression[] @params = new[] { body != null ? body.Parameters[0] : Expression.Variable(typeof(object)) };

            var blockVariables = new List<ParameterExpression>();
            var block = new List<Expression>();

            this.LoadLocals(locals, @params, blockVariables, block);

            Expression body2 = this.VisitBodyLambda(body);

            if (body2 != null)
            {
                block.Add(body2);
            }

            if (@params != null)
            {
                this.UnloadLocals(@params);
            }

            return block.Count == 0 ? Expression.Empty() : blockVariables.Count == 0 && block.Count == 1 ? block[0] : Expression.Block(blockVariables, block);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression AssignTStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            Expression left = this.FixIndexers(this.Visit(node.Arguments[0]));
            Expression right = this.Visit(node.Arguments[1]);

            Expression expression = Expression.Assign(left, right ?? Expression.Default(left.Type));
            return expression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression PreIncrementTStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            Expression left = this.FixIndexers(this.Visit(node.Arguments[0]));

            Expression expression = Expression.PreIncrementAssign(left);
            return expression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression PreDecrementTStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            Expression left = this.FixIndexers(this.Visit(node.Arguments[0]));

            Expression expression = Expression.PreDecrementAssign(left);
            return expression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression PostIncrementTStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            Expression left = this.FixIndexers(this.Visit(node.Arguments[0]));

            Expression expression = Expression.PostIncrementAssign(left);
            return expression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression PostDecrementTStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            Expression left = this.FixIndexers(this.Visit(node.Arguments[0]));

            Expression expression = Expression.PostDecrementAssign(left);
            return expression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression IncreaseTStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            Expression left = this.FixIndexers(this.Visit(node.Arguments[0]));
            Expression right = this.Visit(node.Arguments[1]);

            Expression expression = Expression.AddAssign(left, right);
            return expression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression DecreaseTStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            Expression left = this.FixIndexers(this.Visit(node.Arguments[0]));
            Expression right = this.Visit(node.Arguments[1]);

            Expression expression = Expression.SubtractAssign(left, right);
            return expression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression ValueTDefaultValueTStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            Expression expression = method == ValueT ? this.Visit(node.Arguments[0]) : null;

            Type type = null;

            if (expression == null || expression.Type == typeof(void) || (expression.Type == typeof(Statement) && (type = node.Method.GetGenericArguments()[0]) != expression.Type))
            {
                Expression expression2 = Expression.Default(type ?? node.Method.GetGenericArguments()[0]);

                expression = expression == null ? expression2 : Expression.Block(new[] { expression, expression2 });
            }

            return expression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression LabelStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            ConstantExpression name = this.ExpressionOfType<ConstantExpression>(this.Visit(node.Arguments[0]));

            string name2 = (string)name.Value;

            if (this.Labels.ContainsKey(name2))
            {
                throw new NotSupportedException();
            }

            LabelTarget label;

            if (this.MissingLabels.TryGetValue(name2, out label))
            {
                this.MissingLabels.Remove(name2);
            }
            else
            {
                label = Expression.Label(name2);
            }

            this.Labels.Add(name2, label);

            Expression expression = Expression.Label(label);
            return expression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression GotoStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            ConstantExpression name = this.ExpressionOfType<ConstantExpression>(this.Visit(node.Arguments[0]));

            string name2 = (string)name.Value;

            LabelTarget label;

            if (!this.Labels.TryGetValue(name2, out label) && !this.MissingLabels.TryGetValue(name2, out label))
            {
                label = Expression.Label(name2);
                this.MissingLabels.Add(name2, label);
            }

            Expression expression = Expression.Goto(label);
            return expression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression WhileStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            LabelTarget @break = Expression.Label();
            LabelTarget @continue = Expression.Label();

            this.Breaks.Enqueue(Tuple.Create(@break, @continue));

            try
            {
                Expression condition = this.Visit(node.Arguments[0]);
                Expression body = this.ExpressionOfType<Expression>(this.Visit(node.Arguments[1])) ?? Expression.Empty();
                Expression body2 = Visitor.MakeVoid(body);

                Expression expression = ExpressionEx.While(condition, body2, @break, @continue);
                return expression;
            }
            finally
            {
                this.Breaks.Dequeue();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression DoWhileStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            LabelTarget @break = Expression.Label();
            LabelTarget @continue = Expression.Label();

            this.Breaks.Enqueue(Tuple.Create(@break, @continue));

            try
            {
                Expression body = this.ExpressionOfType<Expression>(this.Visit(node.Arguments[0])) ?? Expression.Empty();
                Expression body2 = Visitor.MakeVoid(body);
                Expression condition = this.Visit(node.Arguments[1]);

                Expression expression = ExpressionEx.DoWhile(body2, condition, @break, @continue);
                return expression;
            }
            finally
            {
                this.Breaks.Dequeue();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression DoUntilStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            LabelTarget @break = Expression.Label();
            LabelTarget @continue = Expression.Label();

            this.Breaks.Enqueue(Tuple.Create(@break, @continue));

            try
            {
                Expression body = this.ExpressionOfType<Expression>(this.Visit(node.Arguments[0])) ?? Expression.Empty();
                Expression body2 = Visitor.MakeVoid(body);
                Expression condition = this.Visit(node.Arguments[1]);

                Expression expression = ExpressionEx.DoUntil(body2, condition, @break, @continue);
                return expression;
            }
            finally
            {
                this.Breaks.Dequeue();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression ForTStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            Expression locals = node.Arguments[0];
            LambdaExpression condition = this.ExpressionOfType<LambdaExpression>(node.Arguments[1]);
            LambdaExpression adder = this.ExpressionOfType<LambdaExpression>(node.Arguments[2]);
            LambdaExpression body = this.ExpressionOfType<LambdaExpression>(node.Arguments[3]);

            LabelTarget @break = Expression.Label();
            LabelTarget @continue = Expression.Label();

            var blockVariables = new List<ParameterExpression>();
            var init = new List<Expression>();

            this.Breaks.Enqueue(Tuple.Create(@break, @continue));

            try
            {
                ParameterExpression[] @params = null;

                if (locals != null)
                {
                    var param2 = new List<ParameterExpression>();

                    if (condition != null)
                    {
                        param2.Add(condition.Parameters[0]);
                    }

                    if (body != null)
                    {
                        param2.Add(body.Parameters[0]);
                    }

                    if (adder != null)
                    {
                        param2.Add(adder.Parameters[0]);
                    }

                    @params = param2.ToArray();

                    this.LoadLocals(locals, @params, blockVariables, init);
                }

                Expression condition2 = this.VisitBodyLambda(condition) ?? Expression.Empty();
                Expression body2 = this.VisitBodyLambda(body) ?? Expression.Empty();
                Expression body3 = Visitor.MakeVoid(body2);
                Expression adder2 = this.VisitBodyLambda(adder) ?? Expression.Empty();

                Expression expression = ExpressionEx.For(adder2, condition2, body3, @break, @continue);

                if (@params != null)
                {
                    init.Add(expression);
                    expression = Expression.Block(blockVariables, Expression.Block(init));
                    this.UnloadLocals(@params);
                }

                return expression;
            }
            finally
            {
                this.Breaks.Dequeue();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression ForEachTStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            LabelTarget @break = Expression.Label();
            LabelTarget @continue = Expression.Label();

            this.Breaks.Enqueue(Tuple.Create(@break, @continue));

            try
            {
                Expression enumerable = this.Visit(node.Arguments[0]);

                if (enumerable == null)
                {
                    throw new NotSupportedException();
                }

                LambdaExpression body = this.ExpressionOfType<LambdaExpression>(node.Arguments[1]);

                if (body == null)
                {
                    throw new NotSupportedException();
                }

                Expression body2 = this.VisitBodyLambda(body) ?? Expression.Empty();
                Expression body3 = Visitor.MakeVoid(body2);
                LambdaExpression body4 = Expression.Lambda(body3, body.Parameters);

                Expression expression = ExpressionEx.ForEach(enumerable, body4, @break, @continue);
                return expression;
            }
            finally
            {
                this.Breaks.Dequeue();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression BreakStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            Expression expression = Expression.Break(this.Breaks.Peek().Item1);
            return expression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression ContinueStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            Expression expression = Expression.Continue(this.Breaks.Peek().Item2);
            return expression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression IfElseStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            Expression condition = this.Visit(node.Arguments[0]);

            if (condition == null)
            {
                throw new NotSupportedException();
            }

            Expression success = this.Visit(node.Arguments[1]) ?? Expression.Empty();
            Expression failure = node.Arguments.Count > 2 ? this.Visit(node.Arguments[2]) : null;

            Expression expression = Expression.IfThenElse(condition, success, failure ?? Expression.Empty());
            return expression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression IfElseIfStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            Expression failure = this.Visit(node.Arguments[0]);
            NewArrayExpression conditions = this.ExpressionOfType<NewArrayExpression>(node.Arguments[1]);

            if (conditions == null || conditions.Expressions.Count == 0)
            {
                return failure ?? Expression.Empty();
            }

            Expression expression = null;

            if (failure != null)
            {
                expression = failure;
            }

            for (int i = conditions.Expressions.Count - 1; i >= 0; i--)
            {
                ReadOnlyCollection<Expression> arguments;

                var expression2 = conditions.Expressions[i] as MethodCallExpression;

                if (expression2 != null)
                {
                    if (expression2.Method != Visitor.TupleBoolActionCreate && expression2.Method != Visitor.MakeIf)
                    {
                        throw new NotSupportedException();
                    }

                    arguments = expression2.Arguments;
                }
                else
                {
                    var expression3 = conditions.Expressions[i] as NewExpression;

                    if (expression3.Type != typeof(Tuple<bool, Statement>))
                    {
                        throw new NotSupportedException();
                    }

                    if (expression3 != null)
                    {
                        arguments = expression3.Arguments;
                    }
                    else
                    {
                        throw new NotSupportedException();
                    }
                }

                Expression condition = this.Visit(arguments[0]);

                if (condition == null)
                {
                    throw new NotSupportedException();
                }

                Expression success = this.Visit(arguments[1]) ?? Expression.Empty();

                if (expression != null)
                {
                    expression = Expression.IfThenElse(condition, success, expression);
                }
                else
                {
                    expression = Expression.IfThen(condition, success);
                }
            }

            return expression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression SwitchDefaultTStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            bool hasDefault = method == SwitchDefaultT;

            Expression value = this.Visit(node.Arguments[0]);
            Expression @default = hasDefault ? this.Visit(node.Arguments[1]) : null;
            NewArrayExpression conditions = this.ExpressionOfType<NewArrayExpression>(node.Arguments[hasDefault ? 2 : 1]);

            Type type = value.Type;

            SwitchCase[] switchCases = conditions != null ? new SwitchCase[conditions.Expressions.Count] : new SwitchCase[0];

            if (conditions == null || conditions.Expressions.Count == 0)
            {
                // Expressin.Switch doesn't support zero-sized Switch cases.
                // We solve by not using the Switch case and building directly a Block expression.
                ParameterExpression temp = Expression.Variable(value.Type);

                var block = new List<Expression>();

                block.Add(Expression.Assign(temp, value));

                if (@default != null)
                {
                    block.Add(@default);
                }

                Expression expression = Expression.Block(new[] { temp }, block);

                return expression;
            }
            else
            {
                Type typeArray = type.MakeArrayType();

                Type tupleType = typeof(Tuple<,>).MakeGenericType(typeArray, typeof(Statement));

                MethodInfo tupleCreate = TupleCreate.MakeGenericMethod(typeArray, typeof(Statement));
                MethodInfo makeCaseSingle = MakeCaseSingleT.MakeGenericMethod(type);
                MethodInfo makeCaseArray = MakeCaseArrayT.MakeGenericMethod(type);

                for (int i = 0; i < switchCases.Length; i++)
                {
                    ReadOnlyCollection<Expression> arguments;

                    var expression2 = conditions.Expressions[i] as MethodCallExpression;

                    Expression conditions2;

                    if (expression2 != null)
                    {
                        if (expression2.Method != tupleCreate && expression2.Method != makeCaseSingle && expression2.Method != makeCaseArray)
                        {
                            throw new NotSupportedException();
                        }

                        arguments = expression2.Arguments;
                        conditions2 = expression2.Method != makeCaseSingle ? arguments[0] : Expression.NewArrayInit(type, arguments[0]);
                    }
                    else
                    {
                        var expression3 = conditions.Expressions[i] as NewExpression;

                        if (expression3.Type != tupleType)
                        {
                            throw new NotSupportedException();
                        }

                        if (expression3 != null)
                        {
                            arguments = expression3.Arguments;
                            conditions2 = arguments[0];
                        }
                        else
                        {
                            throw new NotSupportedException();
                        }
                    }

                    // It MUST be a new[] { } because the size must be "knowable" at statement-compile time.
                    NewArrayExpression conditions3 = this.ExpressionOfType<NewArrayExpression>(this.Visit(conditions2));

                    if (conditions3 == null)
                    {
                        throw new NotSupportedException();
                    }

                    Expression success = this.Visit(arguments[1]) ?? Expression.Empty();

                    switchCases[i] = Expression.SwitchCase(success, conditions3.Expressions);
                }

                Expression expression;

                Type type2 = @default == null ? typeof(void) : this.FindCommonType(new[] { @default }.Concat(switchCases.Select(p => p.Body)));

                expression = Expression.Switch(type2, value, @default, null, switchCases);

                return expression;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression LockStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            Expression obj = this.Visit(node.Arguments[0]);
            Expression body = this.Visit(node.Arguments[1]);

            Expression expression = ExpressionEx.Lock(obj, body);
            return expression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression TryLockStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            Expression obj = this.Visit(node.Arguments[0]);
            Expression timeout = this.Visit(node.Arguments[1]);
            LambdaExpression body = this.ExpressionOfType<LambdaExpression>(node.Arguments[2]);
            Expression body2 = this.VisitBodyLambda(body);
            LambdaExpression body3 = body2 != null ? Expression.Lambda(body2, body.Parameters) : Expression.Lambda(Expression.Empty(), Expression.Parameter(typeof(bool)));

            Expression expression = ExpressionEx.TryLock(obj, timeout, body3);
            return expression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression UsingTStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            Expression obj = this.Visit(node.Arguments[0]);
            LambdaExpression body = this.ExpressionOfType<LambdaExpression>(node.Arguments[1]);
            Expression body2 = this.VisitBodyLambda(body);
            Expression body3 = body2 != null ? (Expression)Expression.Lambda(body2, body.Parameters) : Expression.Empty();

            Expression expression = ExpressionEx.Using(obj, body3);
            return expression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression ThrowStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            Expression exception = this.Visit(node.Arguments[0]);

            Expression expression = Expression.Throw(exception);
            return expression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression RethrowStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            Expression expression = Expression.Rethrow();
            return expression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected Expression TryCatchFinallyStatement(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            bool hasFinally = method == TryFinally || method == TryCatchFinally;

            Expression body = this.Visit(node.Arguments[0]);
            Expression @finally = hasFinally ? this.Visit(node.Arguments[1]) : null;
            NewArrayExpression exceptions = !hasFinally || node.Arguments.Count > 2 ? this.ExpressionOfType<NewArrayExpression>(node.Arguments[hasFinally ? 2 : 1]) : null;

            if (exceptions == null || exceptions.Expressions.Count == 0)
            {
                if (body == null)
                {
                    return @finally ?? Expression.Empty();
                }

                if (@finally == null)
                {
                    return body;
                }
            }

            CatchBlock[] catchBlocks = null;

            if (exceptions != null && exceptions.Expressions.Count != 0)
            {
                catchBlocks = new CatchBlock[exceptions.Expressions.Count];

                for (int i = 0; i < catchBlocks.Length; i++)
                {
                    ReadOnlyCollection<Expression> arguments;

                    var expression2 = exceptions.Expressions[i] as MethodCallExpression;

                    if (expression2 != null)
                    {
                        MethodInfo genericMethod;
                        ParameterInfo[] parameters;
                        Type[] genericArguments;

                        if (expression2.Method != Visitor.MakeCatch && (!expression2.Method.IsGenericMethod ||
                            ((genericMethod = expression2.Method.GetGenericMethodDefinition()) != Visitor.MakeCatchT &&
                                (genericMethod != Visitor.TupleCreate ||
                                    (parameters = expression2.Method.GetParameters())[0].ParameterType != typeof(Type) ||
                                        !parameters[1].ParameterType.IsGenericType ||
                                        parameters[1].ParameterType.GetGenericTypeDefinition() != typeof(Func<,>) ||
                                        !typeof(Exception).IsAssignableFrom((genericArguments = parameters[1].ParameterType.GetGenericArguments())[0]) ||
                                        !typeof(Statement).IsAssignableFrom(genericArguments[1])))))
                        {
                            throw new NotSupportedException();
                        }

                        arguments = expression2.Arguments;
                    }
                    else
                    {
                        var expression3 = exceptions.Expressions[i] as NewExpression;

                        if (expression3.Type != typeof(Tuple<Type, Func<Exception, Statement>>))
                        {
                            throw new NotSupportedException();
                        }

                        if (expression3 != null)
                        {
                            arguments = expression3.Arguments;
                        }
                        else
                        {
                            throw new NotSupportedException();
                        }
                    }

                    ConstantExpression exception = this.ExpressionOfType<ConstantExpression>(this.Visit(arguments[0]));

                    if (exception == null)
                    {
                        throw new NotSupportedException();
                    }

                    var type = (Type)exception.Value;

                    LambdaExpression action = this.ExpressionOfType<LambdaExpression>(arguments[1]);

                    Type parameterType = action != null ? action.Parameters[0].Type : null;

                    if (parameterType != null && !parameterType.IsAssignableFrom(type))
                    {
                        throw new NotSupportedException();
                    }

                    Expression action2 = this.VisitBodyLambda(action);

                    if (action2 == null)
                    {
                        catchBlocks[i] = Expression.MakeCatchBlock(type, null, Expression.Empty(), null);
                    }
                    else if (parameterType != null && type != parameterType)
                    {
                        ParameterExpression exception2 = Expression.Variable(type);
                        Expression action3 = Expression.Block(new[] { action.Parameters[0] }, new Expression[] { Expression.Assign(action.Parameters[0], exception2), action2 });

                        catchBlocks[i] = Expression.MakeCatchBlock(type, exception2, action3, null);
                    }
                    else
                    {
                        catchBlocks[i] = Expression.MakeCatchBlock(type, action.Parameters[0], action2, null);
                    }
                }
            }

            Expression expression;

            body = body ?? Expression.Empty();

            expression = Expression.MakeTry(catchBlocks != null ? this.FindCommonType(new[] { body }.Concat(catchBlocks.Select(p => p.Body))) : body.Type, body, @finally, null, catchBlocks);
            return expression;
        }
    }
}
